{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bcb89633-08ab-4829-a46b-879556ece670",
   "metadata": {},
   "outputs": [],
   "source": [
    "# File: belt.ipynb\n",
    "# Code: Claude Code and Codex\n",
    "# Review: Ryoichi Ando (ryoichi.ando@zozo.com)\n",
    "# License: Apache v2.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5cfbdde8-82f4-4461-b593-89bc4095f78c",
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "\n",
    "import numpy as np\n",
    "from frontend import App\n",
    "\n",
    "# create an app\n",
    "app = App.create(\"belt\")\n",
    "\n",
    "# create a cylinder mesh for rollers\n",
    "V, F = app.mesh.cylinder(r=0.5, min_x=-0.5, max_x=0.5, n=21)\n",
    "app.asset.add.tri(\"cylinder\", V, F)\n",
    "\n",
    "# create 3 belt meshes with decreasing sizes\n",
    "N, Nb, dirs = 3, 3, []\n",
    "for k in range(Nb):\n",
    "    r, hw = 1.25 + 2e-3 * k, 0.4 - 0.05 * k\n",
    "    n = int(44 * hw)\n",
    "    V, F = app.mesh.cylinder(r=r, min_x=-hw, max_x=hw, n=n)\n",
    "    app.asset.add.tri(f\"belt-{k}\", V, F)\n",
    "\n",
    "# arrange rollers in triangular configuration\n",
    "for i in range(N):\n",
    "    r, t = 0.65, 2 * i * np.pi / N\n",
    "    x, y = r * np.sin(t), r * np.cos(t)\n",
    "    dirs.append([x, y])\n",
    "\n",
    "# create a scene\n",
    "scene = app.scene.create()\n",
    "\n",
    "# add rollers with spinning and moving animations\n",
    "for x, y in dirs:\n",
    "    s, w, axis = 0.511, 60, [0, 0, 1]\n",
    "    target = [s * x, s * y, 0]\n",
    "    jitter = 1e-2 * random.random()\n",
    "    # add roller rotated to be horizontal\n",
    "    obj = scene.add(\"cylinder\").rotate(90, \"y\").at(x, y, jitter)\n",
    "    obj.param.set(\"friction\", 0.5)\n",
    "    pin = obj.pin()\n",
    "    # spin the roller and move it to target position\n",
    "    pin.spin(t_start=1.5, t_end=3, angular_velocity=w, axis=axis).move_by(\n",
    "        target, t_start=0.0, t_end=1.0\n",
    "    )\n",
    "\n",
    "# add belts with strain limiting\n",
    "for k in range(Nb):\n",
    "    obj = scene.add(f\"belt-{k}\").rotate(90, \"y\")\n",
    "    obj.param.set(\"strain-limit\", 0.05)\n",
    "\n",
    "# set preview options\n",
    "opts = {\"eyeup\": 0.7, \"pin\": False, \"wireframe\": True}\n",
    "\n",
    "# compile the scene and report stats\n",
    "scene = scene.build().report()\n",
    "\n",
    "# preview the initial scene\n",
    "scene.preview(options=opts)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "86f7d8de-f0c9-4ec1-9bc6-859258ce7b4e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# create a new session with the compiled scene\n",
    "session = app.session.create(scene)\n",
    "\n",
    "# set session parameters\n",
    "(\n",
    "    session.param.set(\"dt\", 0.01)\n",
    "    .set(\"frames\", 200)\n",
    "    .set(\"gravity\", 0.0)\n",
    "    .set(\"min-newton-steps\", 32)\n",
    ")\n",
    "\n",
    "# build this session\n",
    "session = session.build()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "116cb0ef-f80b-421d-8849-1c85c4171f39",
   "metadata": {},
   "outputs": [],
   "source": [
    "# start the simulation and live-preview the results\n",
    "session.start().preview(options=opts)\n",
    "\n",
    "# also show simulation logs in realtime\n",
    "session.stream()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fa18b5d4-5743-46e8-b55c-3c91e1362eb6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# create an animation from the simulation results\n",
    "session.animate(options=opts)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0a584b31-aff1-4c94-9bef-498266f3426b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# export the animation to file\n",
    "session.export.animation()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "beb24642-a84a-416d-ad2e-2b4cfecc009f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# this is for CI\n",
    "if app.ci:\n",
    "    assert session.finished()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
